package nels4561;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import spacewar2.objects.Base;
import spacewar2.objects.Ship;
import spacewar2.simulator.Toroidal2DPhysics;
import spacewar2.utilities.Position;


/**
 * Assigns each ship a base to patrol around. Also
 * handles new base locations
 * 
 * @author Michael & Thomas
 */
public class ShipCoordination {
	
	/**
	 * The height, width of the space
	 */
	public int _height;
	public int _width;
	/**
	 * Space passed to us
	 */
	public Toroidal2DPhysics _space;
	/**
	 * Our main, undestroyable base
	 */
	public Base _homeBase;
	/**
	 * The future base positions, based off our main base's location
	 */
	public Position base1;
	public Position base2;
	public Position base3;
	public Position base4;
	/**
	 * Number of bases and ships
	 */
	public int _numBases = 0;
	public int _numShips = 0;
	/**
	 * Our team's name
	 */
	public String _teamName;
	/**
	 * Lists of our bases and ships
	 */
	public ArrayList<Base> _bases;
	public ArrayList<Ship> _ships;
	/**
	 * Hashmap of base and ship assignments
	 */
	public HashMap<Base, ArrayList<Ship>> _baseToShipAssignments;
	/**
	 * Patrol radius
	 */
	public HashMap<Base, Integer> _baseRadius;
	
	public static int _radius = 500;
	
	/**
	 * Our constructor for this class. Initializes the base and ship
	 * list and assigns ships to bases as they are added.
	 * @param space
	 * @param teamName
	 */
	public ShipCoordination(Toroidal2DPhysics space, String teamName)
	{
		_height = space.getHeight();
		_width = space.getWidth();
		
		_space = space;
		
		_bases = new ArrayList<Base>();
		_ships = new ArrayList<Ship>();
		
		_baseToShipAssignments = new HashMap<Base, ArrayList<Ship>>();
		_baseRadius = new HashMap<Base, Integer>();
		_teamName = teamName;
		
		for (Base base : space.getBases())
		{
			if (base.getTeamName().equals(_teamName))
			{
				_numBases++;
				_bases.add(base);
				setBaseRadius(base, _radius);
				
				if (base.isHomeBase())
				{
					_homeBase = base;
				}
			}
		}
		
		for (Ship ship : space.getShips())
		{
			if (ship.getTeamName().equals(_teamName))
			{
				_numShips++;
				_ships.add(ship);
				pickShipToBase(ship);
			}
		}
	}
	
	/**
	 * Method to assign a ship to a base. Each base is assigned one
	 * ship, unless we have more ships than bases. In which case, we 
	 * assign multiple bases to the home base and increase its patrol radius.
	 * @param ship
	 * @param base
	 */
	public void addShipToBase(Ship ship, Base base)
	{
		ArrayList<Ship> ships = _baseToShipAssignments.get(base);
		if (ships != null)
		{
			ships.add(ship);
		}
		else 
		{
			ships = new ArrayList<Ship>();
			ships.add(ship);
		}
		_baseToShipAssignments.put(base, ships);
		
		if (base.isHomeBase())
		{
			int radius = _baseRadius.get(base);
			_baseRadius.put(base, radius + (ships.size()-1)*200);
		}
	}
	
	/**
	 * Setter for the base radius
	 * @param base
	 * @param radius
	 */
	public void setBaseRadius(Base base, int radius)
	{
		_baseRadius.put(base, radius);
	}
	
	/**
	 * Calculates the next base position. Home is the origin
	 * and the bases are put in the four quadrants of the map
	 * @return
	 */
	public Position decideNewBasePosition()
	{
		Position homePos = _homeBase.getPosition();
		double homeX = homePos.getX();
		double homeY = homePos.getY();
		
		Random rand = new Random();
		
		int freeRadius = Base.BASE_RADIUS;
		int maxDistance = 100;
		
		switch(_numBases)
		{
			case 1:
				if (base1==null)
				{
					base1 = _space.getRandomFreeLocationInRegion(rand, freeRadius, 
							(int)homeX + _width/4, (int)homeY - _height/4, maxDistance);
				}
				return base1;
			
			case 2:
				if (base2 == null)
				{
					base2=_space.getRandomFreeLocationInRegion(rand, freeRadius, 
							(int)homeX - _width/4, (int)homeY - _height/4, maxDistance);
				}
				return base2;
				
			case 3:
				if (base3 == null)
				{
					base3=_space.getRandomFreeLocationInRegion(rand, freeRadius, 
							(int)homeX - _width/4, (int)homeY + _height/4, maxDistance);
				}
				return base3;
			
			case 4:
				if (base4 == null)
				{
					base4=_space.getRandomFreeLocationInRegion(rand, freeRadius, 
							(int)homeX + _width/4, (int)homeY + _height/4, maxDistance);
				}
				return base4;
				
			default:
				return null;
		}
	}
	
	/**
	 * Given a ship, calculate the appropriate base to assign it to.
	 * @param ship
	 */
	public void pickShipToBase(Ship ship)
	{
		ArrayList<Ship> assign = new ArrayList<Ship>();
		ArrayList<Ship> baseShips;
		assign.add(ship);
		
		for (Base base : _bases)
		{
			baseShips = _baseToShipAssignments.get(base);
			if (baseShips != null)
			{
				if (baseShips.size() == 0)
				{
					addShipToBase(ship, base);
					return;
				}
			}
			else
			{
				addShipToBase(ship, base);
				return;
			}
		}
		
		addShipToBase(ship, _homeBase);
	}
	
	/**
	 * Redistribute ship assignments. Will usually only be called
	 * when we have more ships than bases and then we buy a base.
	 */
	public void redistributeShipAssignments()
	{
		ArrayList<Ship> baseShips;
		for (Base base: _bases)
		{
			baseShips = _baseToShipAssignments.get(base);
			if (baseShips != null )
			{
				if (baseShips.size() == 0 
						&& _baseToShipAssignments.get(_homeBase).size() > 1)
				{
					ArrayList<Ship> homeBaseShips = _baseToShipAssignments.get(_homeBase);
					Ship move = homeBaseShips.remove(homeBaseShips.size()-1);
					addShipToBase(move, base);
				}
			}
			else
			{
				ArrayList<Ship> homeBaseShips = _baseToShipAssignments.get(_homeBase);
				Ship move = homeBaseShips.remove(homeBaseShips.size()-1);
				addShipToBase(move, base);
			}
		}
	}
	
	/**
	 * Add a ship, increment the ship counter, and assign
	 * it to a base.
	 * @param ship
	 */
	public void addShip(Ship ship)
	{
		_numShips++;
		_ships.add(ship);
		pickShipToBase(ship);
	}
	
	/**
	 * Add a base, increment the base counter, and
	 * set the patrol radius.
	 * @param base
	 */
	public void addBase(Base base)
	{
		_numBases++;
		_bases.add(base);
		setBaseRadius(base, _radius);
	}
	
	/** 
	 * Add base was destroyed. Decrement the counter and
	 * remove it and all assignments form the hashmap.
	 * @param base
	 */
	public void baseDestroyed(Base base)
	{
		_numBases--;
		_baseToShipAssignments.remove(base);
		_baseRadius.remove(base);
		_bases.remove(base);
	}

}
